### Ubuntu and Debian/Raspbian (Qt6)
```sh
apt install --no-install-recommends build-essential autoconf automake libtool make libjack-jackd2-dev git help2man libclang-dev libdbus-1-dev libdbus-1-dev python3-jinja2
-apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6svg6 libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window
+apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6svg6 libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window qml6-module-qtquick-dialogs
apt install qt6-base-dev qt6-base-dev-tools qmake6 qt6-tools-dev qt6-declarative-dev qt6-webengine-dev qt6-webview-dev qt6-webview-plugins libqt6svg6-dev libqt6websockets6-dev libqt6core5compat6-dev libqt6shadertools6-dev libgl1-mesa-dev
# for GUI builds
apt install libfreetype6-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libx11-xcb-dev libdrm-dev libglu1-mesa-dev libwayland-dev libwayland-egl1-mesa libgles2-mesa-dev libwayland-server0 libwayland-egl-backend-dev libxcb1-dev libxext-dev libfontconfig1-dev libxrender-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev '^libxcb.*-dev' libxcb-render-util0-dev libxcomposite-dev libgtk-3-dev
--- /dev/null
+## JackTrip network protocol (as implemented)
+
+This document describes JackTrip’s **on-the-wire protocol** as implemented in the current source tree. It is intended for developers debugging or interoperating with JackTrip at the packet level.
+
+### Scope and non-goals
+
+- **In scope**: the real-time **UDP audio stream**, its headers and payload layout, the optional **UDP redundancy** framing, the small **UDP “stop” control packet**, and the **TCP handshake** used by hub/ping-server style deployments (including the authentication variant).
+- **Out of scope**: local-only IPC (e.g. `QLocalSocket` “AudioSocket”), OSC control, and any higher-level application semantics outside packet exchange.
+
+### Transports at a glance
+
+- **UDP (audio)**: real-time audio is sent as UDP datagrams containing `PacketHeader` + raw audio payload.
+- **UDP (control)**: a small fixed-size “stop” datagram is used to signal shutdown.
+- **TCP (hub/ping-server handshake)**: a short-lived TCP connection is used to exchange ephemeral UDP port information (and optionally do TLS + credentials). The client sends 4 bytes representing the port number it is binding to, and the server responds by sending 4 bytes representing its own port number.
+
+---
+
+## UDP audio datagrams
+
+### High-level framing
+
+Each UDP datagram carries one of:
+
+- **Audio datagram**: one or more **full packets** (header + audio payload). When redundancy is disabled, there is exactly one full packet per UDP datagram. When redundancy is enabled, multiple full packets are concatenated into a single UDP datagram to provide forward error correction (FEC) (see “UDP redundancy”).
+- **Stop/control datagram**: exactly 63 bytes of `0xFF` (see “UDP stop/control datagram”).
+
+### Packet header types
+
+The header is selected by `DataProtocol::packetHeaderTypeT`:
+
+- **DEFAULT**: `DefaultHeaderStruct` (the standard JackTrip header).
+- **JAMLINK**: `JamLinkHeaderStuct` (JamLink compatibility).
+- **EMPTY**: no header (payload only).
+
+See `src/PacketHeader.h` and `src/PacketHeader.cpp`.
+
+### Default header (`DEFAULT`)
+
+On-wire layout is the in-memory `DefaultHeaderStruct` copied with `memcpy()` (no explicit endian conversions).
+
+Fields (in order):
+
+| Field | Type | Meaning |
+|------:|------|---------|
+| `TimeStamp` | `uint64_t` | Timestamp in microseconds since Unix epoch (see `PacketHeader::usecTime()`). |
+| `SeqNumber` | `uint16_t` | Sequence number; increments once per audio period and wraps at 16 bits. |
+| `BufferSize` | `uint16_t` | Audio period size \(N\) in **samples per channel**. |
+| `SamplingRate` | `uint8_t` | Encoded sample-rate enum value (`AudioInterface::samplingRateT`), **not** Hz. |
+| `BitResolution` | `uint8_t` | Bits per sample (8/16/24/32). |
+| `NumIncomingChannelsFromNet` | `uint8_t` | Channel count expected from the peer “from network” direction (see notes below). |
+| `NumOutgoingChannelsToNet` | `uint8_t` | Channel count the sender is placing into the payload (see notes below). |
+
+#### Important interoperability notes
+
+- **Endianness / ABI**: this header is serialized by raw `memcpy()` of a C struct. In practice this assumes:
+ - both sides are using compatible ABI/layout for the struct, and
+ - both sides are on the same endianness (typically **little-endian** on modern desktop platforms).
+- **Channel fields are asymmetric**: the implementation uses these fields to convey “incoming vs outgoing” channel counts, including a couple of sentinel behaviors:
+ - `NumIncomingChannelsFromNet` is populated from local *audio interface output* channel count.
+ - `NumOutgoingChannelsToNet` may be set to `0` when in/out channel counts match, or to `0xFF` when there are zero audio interface input channels.
+
+These behaviors come from `DefaultHeader::fillHeaderCommonFromAudio()` in `src/PacketHeader.cpp`.
+
+### JamLink header (`JAMLINK`)
+
+Please note that JamLink is an obsolete device.
+
+JamLink uses a compact header:
+
+| Field | Type | Meaning |
+|------:|------|---------|
+| `Common` | `uint16_t` | Bitfield describing mono/stereo, bit depth, sample rate, and samples-per-packet (JamLink “streamType”). |
+| `SeqNumber` | `uint16_t` | Sequence number. |
+| `TimeStamp` | `uint32_t` | Timestamp. |
+
+The current implementation primarily fills this for JamLink constraints (mono, 48kHz, 64-sample buffers). See `JamLinkHeader::fillHeaderCommonFromAudio()` in `src/PacketHeader.cpp`.
+
+### Empty header (`EMPTY`)
+
+No header; the UDP payload is raw audio data only.
+
+---
+
+## UDP audio payload
+
+### Size
+
+For a single full packet (no redundancy), the UDP payload length is:
+
+$$\text{headerBytes} + (N \times C \times \text{bytesPerSample})$$
+
+Where:
+
+- \(N\) is `BufferSize` (samples per channel)
+- \(C\) is the number of channels present in the payload
+- `bytesPerSample` is `BitResolution / 8`
+
+### Channel/sample ordering (planar / non-interleaved)
+
+On the wire, the payload is **planar** (non-interleaved) by channel:
+
+- First \(N\) samples for channel 0
+- Then \(N\) samples for channel 1
+- …
+
+This is explicit in `UdpDataProtocol` which converts between:
+
+- **Internal**: interleaved layout \([n][c]\)
+- **Network**: planar layout \([c][n]\)
+
+See `UdpDataProtocol::sendPacketRedundancy()` and `UdpDataProtocol::receivePacketRedundancy()` in `src/UdpDataProtocol.cpp`.
+
+### Sample encoding (bit resolution)
+
+JackTrip processes audio internally as `float` (`sample_t`), but the network payload uses the selected bit resolution via `AudioInterface::fromSampleToBitConversion()` / `fromBitToSampleConversion()`.
+
+Behavior by bit resolution (`AudioInterface::audioBitResolutionT`):
+
+- **8-bit (`BIT8`)**: signed 8-bit integer, scaled from float in \([-1, 1]\).
+- **16-bit (`BIT16`)**: signed 16-bit integer, written **little-endian**.
+- **24-bit (`BIT24`)**: a **non-standard 3-byte format**: a 16-bit signed integer plus an 8-bit unsigned “remainder” byte.
+- **32-bit (`BIT32`)**: raw 32-bit float bytes (`memcpy` of `float`), which implicitly assumes IEEE-754 and matching endianness.
+
+See `src/AudioInterface.cpp`.
+
+---
+
+## UDP redundancy (optional)
+
+JackTrip can send redundant audio packets to reduce audible artifacts from packet loss.
+
+### Framing
+
+With redundancy factor \(R\), each UDP datagram contains **R full packets** concatenated:
+
+- The newest packet is first (`UDP[n]`), followed by older packets (`UDP[n-1]`, …).
+- Total UDP payload length becomes `R * full_packet_size`.
+
+The sender implements this by shifting a buffer and prepending the newest full packet each period.
+
+See `UdpDataProtocol::sendPacketRedundancy()` and the explanatory comment block in `src/UdpDataProtocol.cpp`.
+
+### Receiver behavior
+
+Upon receiving a redundant datagram, the receiver:
+
+- Reads the first packet’s `SeqNumber`.
+- If it is not the next expected sequence, scans forward through the concatenated packets looking for the expected next one.
+- May “revive” and deliver multiple packets from the redundant datagram in order.
+- Treats large negative or implausibly large sequence jumps as **out-of-order** and ignores them.
+
+See `UdpDataProtocol::receivePacketRedundancy()` in `src/UdpDataProtocol.cpp`.
+
+---
+
+## UDP stop/control datagram
+
+JackTrip uses a special fixed-size UDP datagram to signal shutdown:
+
+- **Length**: 63 bytes
+- **Contents**: every byte is `0xFF`
+
+The receiver checks for this exact pattern and treats it as “Peer Stopped”.
+
+See `UdpDataProtocol::processControlPacket()` and the shutdown path in `UdpDataProtocol::run()` in `src/UdpDataProtocol.cpp`.
+
+---
+
+## Connection setup and “handshake”
+
+JackTrip supports multiple deployment styles. The relevant “protocol” differs depending on mode.
+
+### P2P server mode (UDP-only)
+
+In P2P server mode, there is **no TCP handshake**. Instead:
+
+- The server binds a UDP socket on its configured receive port.
+- It waits for the first UDP datagram.
+- It uses the datagram’s source address/port as the peer endpoint for subsequent UDP send/receive.
+
+This supports basic NAT traversal by responding to the client’s observed source port.
+
+See `JackTrip::serverStart()` and `JackTrip::receivedDataUDP()` in `src/JackTrip.cpp`.
+
+### Hub / ping-server mode (TCP handshake + UDP audio)
+
+When connecting to a hub/ping-server style endpoint, JackTrip uses a short-lived TCP connection to exchange UDP port information.
+
+#### Unauthenticated handshake (no TLS)
+
+Client → server (TCP):
+
+- `int32` little-endian: the client’s UDP receive/bind port
+- `gMaxRemoteNameLength` bytes: optional UTF-8 “remote client name” (null-terminated, padded with zeros)
+
+Server → client (TCP):
+
+- `int32` little-endian: the server-assigned UDP port the client should use as its peer port
+
+The TCP connection is then closed.
+
+Client-side send/receive logic: `JackTrip::receivedConnectionTCP()` and `JackTrip::receivedDataTCP()` in `src/JackTrip.cpp`
+Server-side receive/send logic: `UdpHubListener::readClientUdpPort()` and `UdpHubListener::sendUdpPort()` in `src/UdpHubListener.cpp`
+
+#### Authentication / TLS handshake (optional)
+
+This is an extension of the same TCP handshake using values above 65535 as “auth response” codes.
+
+High-level flow:
+
+1. Client connects TCP and sends an `int32` little-endian value of `Auth::OK` to request authentication.
+2. Server replies with an `int32` auth response (e.g. `Auth::OK`, `Auth::NOTREQUIRED`, `Auth::REQUIRED`, …).
+3. If both sides proceed, TLS is established on the same TCP socket.
+4. Client then sends:
+ - `int32` LE: UDP receive/bind port
+ - `gMaxRemoteNameLength` bytes: client name
+ - `int32` LE: username length (excluding null terminator)
+ - `int32` LE: password length (excluding null terminator)
+ - `username` bytes + `\0`
+ - `password` bytes + `\0`
+5. Server validates credentials and replies with either:
+ - `int32` LE UDP port (<= 65535) on success, or
+ - `int32` LE auth error code (> 65535) on failure
+
+Client-side: `JackTrip::receivedConnectionTCP()`, `JackTrip::connectionSecured()`, and `JackTrip::receivedDataTCP()` in `src/JackTrip.cpp`
+Server-side: `UdpHubListener::receivedClientInfo()`, `UdpHubListener::checkAuthAndReadPort()`, and `UdpHubListener::sendUdpPort()` in `src/UdpHubListener.cpp`
+
+---
+
+## QoS marking (best-effort)
+
+On supported platforms, JackTrip attempts to mark UDP packets as “voice” traffic:
+
+- Linux/Unix: sets DSCP to 56 (`IP_TOS` / `IPV6_TCLASS` set to `0xE0`), and sets `SO_PRIORITY` to 6.
+- Windows: uses QOS APIs with `QOSTrafficTypeVoice`.
+- macOS: uses `SO_NET_SERVICE_TYPE` with `NET_SERVICE_TYPE_VO` (best-effort).
+
+See `src/UdpDataProtocol.cpp`.
+
+---
+
+## References
+
+For additional context on JackTrip's network behavior and interpretation of debug output (`-V` flag):
+
+Chafe, C. (2018). I am Streaming in a Room. *Frontiers in Digital Humanities*, Volume 5. https://doi.org/10.3389/fdigh.2018.00027
\ No newline at end of file
+- Version: "2.7.2"
+ Date: 2026-02-06
+ Description:
+ - (added) Documentation for the JackTrip network protocol
+ - (added) Hub server - log client name with UDP port
+ - (fixed) Fixed crash when JACK ran out of available ports
+ - (fixed) Refuse to run if DYLD_INSERT_LIBRARIES is set on OSX
+ - (fixed) Various PLC quality improvements and bug fixes
+ - (fixed) Improved error message when studio connection is lost
+ - (fixed) Suppressed verbose logging of OSC get requests
+ - (fixed) Added some missing Qt dependencies to Linux docs
- Version: "2.7.1"
Date: 2025-06-30
Description:
# install required packages
ENV DEBIAN_FRONTEND=noninteractive
+RUN DEBIAN_BUSTER=$(grep buster /etc/apt/sources.list); \
+ if [ -f /etc/apt/sources.list -a -n "$DEBIAN_BUSTER" ]; then \
+ sed -i s/deb.debian.org/archive.debian.org/g /etc/apt/sources.list; \
+ sed -i '/buster-updates/d' /etc/apt/sources.list; \
+ fi
RUN apt-get update \
&& apt-get install -yq --no-install-recommends curl python3-pip build-essential git libclang-dev libdbus-1-dev cmake ninja-build libjack-dev \
&& apt-get install -yq --no-install-recommends libfreetype6-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libx11-xcb-dev libdrm-dev libglu1-mesa-dev libwayland-dev libwayland-egl1-mesa libgles2-mesa-dev libwayland-server0 libwayland-egl-backend-dev libxcb1-dev libxext-dev libfontconfig1-dev libxrender-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev '^libxcb.*-dev' libxcb-render-util0-dev libxcomposite-dev libgtk-3-dev \
For Debian or Ubuntu:
```
-apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6svg6 libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window qml6-module-qtquick-dialogs libjack-jackd2-0 librtaudio6 libxcb-cursor0
+apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6quickdialogs2-6 libqt6svg6 libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window qml6-module-qtquick-dialogs libjack-jackd2-0 librtaudio6 libxcb-cursor0
```
To install JackTrip as a Linux desktop application:
opt_var.add_cmake_defines({'CMAKE_BUILD_TYPE': 'Debug'})
endif
opt_var.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'})
+ opt_var.add_cmake_defines({'CMAKE_POLICY_VERSION_MINIMUM': '3.5'})
libsamplerate_subproject = cmake.subproject('libsamplerate', options: opt_var)
libsamplerate_dep = libsamplerate_subproject.dependency('samplerate')
found_libsamplerate = libsamplerate_dep.found()
- Development Tools:
- Formatting: DevTools/Formatting.md
- Static Analysis: DevTools/StaticAnalysis.md
+ - Network Protocol: Documentation/NetworkProtocol.md
- Write Documentation: Documentation/MkDocs.md
- About:
- Contributors: About/Contributors.md
mInPorts[i] =
jack_port_register(mClient, inName.toLatin1(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsInput | JackPortIsTerminal, 0);
+ if (mInPorts[i] == nullptr) {
+ std::cerr << "*** JackAudioInterface.cpp: failed to register input port "
+ << inName.toStdString() << "\n";
+ return;
+ }
}
// Create Output Ports
mOutPorts[i] =
jack_port_register(mClient, outName.toLatin1(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput | JackPortIsTerminal, 0);
+ if (mOutPorts[i] == nullptr) {
+ std::cerr << "*** JackAudioInterface.cpp: failed to register output port "
+ << outName.toStdString() << "\n";
+ return;
+ }
}
// Create Broadcast Ports
if (mBroadcast) {
mBroadcastPorts[i] =
jack_port_register(mClient, outName.toLatin1(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput | JackPortIsTerminal, 0);
+ if (mBroadcastPorts[i] == nullptr) {
+ std::cerr << "*** JackAudioInterface.cpp: failed to register broadcast "
+ << "port " << outName.toStdString() << "\n";
+ return;
+ }
}
}
}
// Input Ports are READ ONLY and change as needed (no locks) - make a copy for
// debugging
mInBuffer[i] = (sample_t*)jack_port_get_buffer(mInPorts[i], nframes);
+ if (mInBuffer[i] == nullptr) {
+ std::cerr
+ << "*** JackAudioInterface.cpp: failed to get buffer for input port "
+ << mInPorts[i] << " channel " << i << "/" << getNumInputChannels()
+ << "\n";
+ return -1;
+ }
}
for (int i = 0; i < getNumOutputChannels(); i++) {
// Output Ports are WRITABLE
mOutBuffer[i] = (sample_t*)jack_port_get_buffer(mOutPorts[i], nframes);
+ if (mOutBuffer[i] == nullptr) {
+ std::cerr
+ << "*** JackAudioInterface.cpp: failed to get buffer for output port "
+ << mOutPorts[i] << " channel " << i << "/" << getNumOutputChannels()
+ << "\n";
+ return -1;
+ }
}
//-------------------------------------------------------------------
// TEST: Loopback
}
} else if (msg == "/get") {
const char* key = args.string();
- cout << "OSC: Get request received - key (" << key << ")" << endl;
+ // cout << "OSC: Get request received - key (" << key << ")" << endl;
if (strcmp("latency", key) == 0) {
emit signalLatencyRequested(sender, senderPort);
}
// update headroom
if (mAutoHeadroom < 0) {
// variable headroom: automatically increase to minimize glitch counts
- int glitchesAllowed;
- if (mMsecTolerance >= (mPeerFPPdurMsec * 2)) {
- // calculate glitches allowed if tolerance is above or equal to
- // the duration of two packets
- glitchesAllowed = std::ceil(
- static_cast<float>(AutoHeadroomGlitchTolerance * mSampleRate) / mPeerFPP);
- } else {
- // zero glitches allowed if tolerance is below duration of two packets
- glitchesAllowed = 0;
- // also don't require two intervals in a row (override)
- mSkipAutoHeadroom = false;
- }
+ const int glitchesAllowed = std::ceil(
+ static_cast<float>(AutoHeadroomGlitchTolerance * mSampleRate) / mPeerFPP);
// sanity check: prevent headroom from growing beyond the greater of
// 3x rolling average of max, or 10ms higher than the max latency observed
const int maxHeadroom =
if (mLastMaxLatency > mMsecTolerance + 1) {
// increase headroom enough to cover any skipped packets
mCurrentHeadroom = std::min<double>(
- maxHeadroom, std::ceil(mLastMaxLatency - mMsecTolerance));
+ maxHeadroom,
+ mCurrentHeadroom + std::ceil(mLastMaxLatency - mMsecTolerance));
} else {
++mCurrentHeadroom;
}
const double now = (double)mIncomingTimer.nsecsElapsed() / 1000000.0;
const int lastSeqNumIn = mLastSeqNumIn.load(std::memory_order_acquire);
int skipped = 0;
+ int firstGoodSkipped = -1;
if ((lastSeqNumIn == -1) || (!mInitialized) || (now < mMsecTolerance)) {
// return silence during startup:
// * no packets arrived yet
// * not initialized
// * hasn't run long enough to meet tolerance
- goto ZERO_OUTPUT;
+ memset(mXfrBuffer, 0, mPeerBytes);
+ return false;
+ } else if (mStashedPacket != -1) {
+ // use the stashed packet as the best candidate
+ memcpy(mXfrBuffer, mSlots[mStashedPacket], mPeerBytes);
+ mLastSeqNumOut = mStashedPacket;
+ mStashedPacket = -1;
+ processPacket(false);
+ return false;
} else if (lastSeqNumIn == mLastSeqNumOut) {
- goto UNDERRUN;
+ return underrun(now, lastSeqNumIn);
} else {
// calculate how many new packets we want to look at to
// find the next packet to pull
}
// check if packet's age matches tolerance, or is the best candidate we have
if (mIncomingTiming[next] + mMsecTolerance >= now || i == 0) {
+ if (skipped == 1 && firstGoodSkipped >= 0) {
+ // special case where we are about to skip 1 good packet.
+ // this defers latency adjustments until they are at least
+ // 2 packets wide.
+ mStashedPacket = next;
+ next = firstGoodSkipped;
+ } else if (skipped > 0) {
+ // process a glitch to account for the skipped packets,
+ // but stash and use this good packet on next callback.
+ pullStat->plcOverruns += skipped;
+ mSkipped += skipped;
+ mStashedPacket = next;
+ processPacket(true);
+ return true;
+ }
// next is the best candidate
memcpy(mXfrBuffer, mSlots[next], mPeerBytes);
mLastSeqNumOut = next;
- goto PACKETOK;
+ processPacket(false);
+ return false;
}
- ++mSkipped;
+ if (firstGoodSkipped == -1)
+ firstGoodSkipped = next;
}
-
- // no viable candidate
- goto UNDERRUN;
}
-PACKETOK : {
- pullStat->plcOverruns += skipped;
- if (skipped && !mLastWasGlitch) {
- processPacket(true);
- return true;
- } else
- processPacket(false);
- goto OUTPUT;
-}
+ // no viable candidate
+ return underrun(now, lastSeqNumIn);
+};
-UNDERRUN : {
+//*******************************************************************************
+bool Regulator::underrun(const double now, const int lastSeqNumIn)
+{
pullStat->plcUnderruns++; // count late
if ((mLastSeqNumOut == lastSeqNumIn)
&& ((now - mIncomingTiming[mLastSeqNumOut]) > gUdpWaitTimeout)) {
- goto ZERO_OUTPUT;
+ memset(mXfrBuffer, 0, mPeerBytes);
+ return false;
}
// "good underrun", not a stuck client
processPacket(true);
return true;
}
-ZERO_OUTPUT:
- memset(mXfrBuffer, 0, mPeerBytes);
-
-OUTPUT:
- return false;
-};
-
//*******************************************************************************
void Regulator::processPacket(bool glitch)
{
private:
void pushPacket(const int8_t* buf, int seq_num);
void updatePushStats(int seq_num);
- bool pullPacket(); // returns true if PLC prediction
+ bool pullPacket(); // returns true if PLC prediction
+ bool underrun(const double now, const int lastSeqNumIn);
bool enableWorker(); // returns true if worker was enabled
void updateTolerance(int glitches, int skipped);
void setFPPratio(int len);
StdDev* pullStat = nullptr;
double mMsecTolerance = 64;
int mLastSeqNumOut = -1;
+ int mStashedPacket = -1;
std::atomic<int> mLastSeqNumIn;
QElapsedTimer mIncomingTimer;
std::vector<double> mIncomingTiming;
// Assign server port and send it to Client
if (id != -1) {
cout << "JackTrip HUB SERVER: Sending Final UDP Port to Client: "
- << mJTWorkers->at(id)->getServerPort() << endl;
+ << clientName.toStdString() << " = " << mJTWorkers->at(id)->getServerPort()
+ << endl;
}
if (id == -1
#include "jacktrip_types.h"
-constexpr const char* const gVersion = "2.7.1"; ///< JackTrip version
+constexpr const char* const gVersion = "2.7.2"; ///< JackTrip version
//*******************************************************************************
/// \name Default Values
QCoreApplication* createApplication(int& argc, char* argv[])
{
+#ifdef __APPLE__
+ // Check for the DYLD_INSERT_LIBRARIES environment variable.
+ // Refuse to run if it is set, to avoid code injection attacks.
+ // Just an extra precaution since QtWebEngine requires the entitlement
+ // com.apple.security.cs.allow-dyld-environment-variable
+ // See https://doc.qt.io/qt-6/qtwebengine-deploying.html
+ if (getenv("DYLD_INSERT_LIBRARIES") != nullptr) {
+ std::cout << "Detected environment variable: DYLD_INSERT_LIBRARIES." << std::endl;
+ std::cout << "To run JackTrip, please omit the this environment variable."
+ << std::endl;
+ std::exit(1);
+ }
+#endif
+
// Check for some specific, GUI related command line options.
bool forceGui = false;
bool testGui = false;
msgBox.setWindowTitle(QStringLiteral("No JACK server"));
} else if (errorMessage == QLatin1String("Peer Stopped")) {
// Report the other end quitting as a regular occurance rather than an error.
- msgBox.setText("The Studio has been stopped.");
+ msgBox.setText("Lost connection to the studio.");
msgBox.setWindowTitle(QStringLiteral("Disconnected"));
} else if (errorMessage.startsWith(RtAudioErrorMsg)) {
if (errorMessage.length() > RtAudioErrorMsg.length() + 2) {